package data;

import java.util.List;


public class DataNormalizer
{

    private int dimensions;

    protected List<SequentialData> rawData;

    protected double[] minimum;
    protected double[] maximum;
    protected double[] average;
    protected double[] stdDev;
    protected double[] ranges;


    public void setRawData(List<SequentialData> rawData)
    {
        this.rawData = rawData;
        dimensions = rawData.get(0).getDimension();
    }

    public double[] centralMoment(int m)
    {
        double[] moment = new double[dimensions];
        for (SequentialData sData : rawData)
        {
            for (double[] p : sData.getdataPoints())
                for (int i = 0; i < dimensions; i++)
                {
                    moment[i] += Math.pow(p[i] - average[i], m);
                }
        }

        int listSize = rawData.size();
        for (int i = 0; i < dimensions; i++)
        {
            moment[i] /= listSize;
        }

        return moment;
    }

    public static double[] centralMoment(int m, List<double[]> rawData, double[] average)
    {
        int dimensions = rawData.get(0).length;
        double[] moment = new double[dimensions];
        for (double[] dataPoint : rawData)
        {
            for (int i = 0; i < dimensions; i++)
            {
                moment[i] += Math.pow(dataPoint[i] - average[i], m);
            }
        }

        int listSize = rawData.size();
        for (int i = 0; i < dimensions; i++)
        {
            moment[i] /= listSize;
        }

        return moment;
    }

    public void normalize(NormalizationStyle style)
    {
        System.out.println("Normalizing points with style: " + style);

        switch (style)
        {
            case Range:
                normalizeRange();
                break;
            case StdDev:
                normalizeStdDev();
                break;
            case None:
                break;
            case RangeAndCenter:
                normalizeRAC();
                break;
            case UnitMagnitude:
                normalizeRAC(2);
        }
    }


    private void computeMinMax()
    {
        minimum = new double[dimensions];
        maximum = new double[dimensions];

        for (int i = 0; i < dimensions; i++)
        {
            minimum[i] = Double.POSITIVE_INFINITY;
            maximum[i] = Double.NEGATIVE_INFINITY;
        }

        for (SequentialData s : rawData)
        {
            for (double[] dataPoint : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    if (dataPoint[i] < minimum[i])
                    {
                        minimum[i] = dataPoint[i];
                    }
                    if (dataPoint[i] > maximum[i])
                    {
                        maximum[i] = dataPoint[i];
                    }
                }
            }

        }
    }


    private void computeAverage()
    {
        average = new double[dimensions];

        int dp = 0;

        for (SequentialData s : rawData)
        {
            for (double[] p : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    average[i] += p[i];
                    dp++;
                }
            }
        }
        for (int i = 0; i < dimensions; i++)
        {
            average[i] = average[i] / dp;
        }
    }


    private void computeStdDev()
    {
        stdDev = centralMoment(2); //get variance

        for (int i = 0; i < dimensions; i++)
        {
            stdDev[i] = Math.sqrt(stdDev[i]);
        }

    }


    private double computeRanges()
    {
        ranges = new double[dimensions];
        double minRange = Double.POSITIVE_INFINITY;
        for (int i = 0; i < dimensions; i++)
        {
            ranges[i] = maximum[i] - minimum[i];
            if (ranges[i] < minRange)
            {
                minRange = ranges[i];
            }
        }

        return minRange;
    }


    private void normalizeStdDev()
    {
        computeAverage();
        computeStdDev();

        for (SequentialData s : rawData)
        {
            for (double[] p : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    double diff = p[i] - average[i];
                    p[i] = diff / stdDev[i];

                }
            }

        }

    }


    private void normalizeRAC()
    {
        normalizeRAC(-1);
    }


    private void normalizeRAC(double range)
    {
        computeAverage();
        computeMinMax();
        double mr = computeRanges();
        range = range < 0 ? mr : range;

        for (SequentialData s : rawData)
        {
            for (double[] dataPoint : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    double factor = range / ranges[i];
                    double offsetVal = dataPoint[i] - average[i];
                    dataPoint[i] = offsetVal * factor;
                }
            }

        }

    }

    public void normalizeMoment(int m)
    {
        System.out.println("Normalizing around " + m + "'th Standard moment");
        computeAverage();
        double[] moment = centralMoment(m);
        computeStdDev();

        for (SequentialData s : rawData)
        {
            for (double[] p : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    double factor = moment[i] / Math.pow(stdDev[i], m);
                    p[i] = p[i] * factor;
                }
            }
        }

    }


    public void normalizeFixedRange(double range)
    {
        System.out.println("Normalizing Fixed Range [" + -range / 2 + ", " + range / 2 + "]");
        normalizeRAC(range);
    }

    private void normalizeRange()
    {
        computeMinMax();
        double minRange = computeRanges();

        for (SequentialData s : rawData)
        {
            for (double[] p : s.getdataPoints())
            {
                for (int i = 0; i < dimensions; i++)
                {
                    p[i] = p[i] * minRange / ranges[i];
                }
            }
        }


    }

    protected void computeAllStats()
    {
        computeMinMax();
        computeAverage();
        computeRanges();
        computeStdDev();
    }

}
